<?php
/**
 * Modelo de CRUD Abstrato
 * Manipula uma tabela no banco de dados
 * @author Emanuel Melo e Renato Lenz @ PC4 Team
 */
class PC4_Crud_Abstrato { //extends Model {
	
	// o Driver do banco de dados. Nesse caso, o CI->db
	public $db;
	protected $CI;
	public $registro;
	
	// a tabela a ser manipulada
	public $tabela;
	public $letra;
	
	// array de validacoes
	public $validacoes;
	// array de erros
	public $erros;
		
	// TODO: ainda não implementado
	// array de informações auxiliares
	protected $extraidos;
	/**
	 * Construtor recebendo a tabela que será manipulada
	 * @param string $tabela
	 */
	function __construct($tabela, $validacoes = array(), $banco = null) {
		$this->tabela = $tabela;
		$this->letra = substr($this->tabela, 0, 1);

		if (empty($banco)) {
			$this->CI =& get_instance();
			$this->db = $this->CI->db;
		}
		else {
			$this->db = $banco;
		}
		
		$this->extraidos = array();
		if (is_array($validacoes)) {
			$this->validacoes = $validacoes;
		}
		else {
			$this->validacoes = array($validacoes);
		}
		$this->erros = array();
	}
	
	/**
	 * Altera o nome da tabela
	 * @param string $nome
	 */
	function setTabela($nome) {
		$this->tabela = $nome;
		$this->letra = substr($nome, 0, 1);
	}
	
	/**
	 * Faz a validação dos campos
	 * @param array(nome_campo,valor) $campos
	 * @param int $id
	 * @return array $erros para cada campo que não passou na validação
	 */
	function valida(&$campos, $id = 0) {
		$this->erros = array();
		foreach ($this->validacoes as $campo => $validacao) {
			foreach ((array)$validacao as $v) {
				if (is_string($v)) {
					$v = PC4_Util_Validacao_Fabrica::cria($v);
				}
				$v->valida($campos, $campo, $this, $id);
			}
		}
		return $this->erros;
	}
	
	/**
	 * // TODO: ainda não implementado
	 * Extrai informações auxiliares para o array $this->extraidos
	 * @param array $registro
	 * @param string $campo
	 */
	function extrai(&$registro, $campo) {
		$this->extraidos[$campo] = $registro[$campo];
		unset($registro[$campo]);
	}
	// TODO: implementar esse metodo
	/*
	function query($sql, $tipo = 'update', $campo = 'id') {
		// Montando o insere
		foreach ($sql as $variable) {
			;
		}
		$tipos = array(
			'update' => "UPDATE {$this->tabela} SET $sql" . ((!empty($valor)) ? " WHERE $campo = $valor" : null),
			'insert' => "INSERT INTO {$this->tabela} ( $sql )" . ((!empty($valor)) ? " WHERE $campo = $valor" : null);
		);

	}
	*/
	/**
	 * Recebe um array com os campos e insere na base de dados o novo registro
	 * @param array $umRegistro
	 * @return int/array $id inserido ou (array)$erros com mensagens de erro
	 */
	function insere($umRegistro, $debug = false) {
		if($debug){
			d($umRegistro, 'Inserindo');
		}
		$erros = $this->valida($umRegistro);
		$this->registro = $umRegistro;
		if (empty($erros)) {
			$this->db->insert($this->tabela, $umRegistro);
			if($debug){
				d($this->db->last_query(), 'Sql executado.');
				exit;
			}
			$umRegistro['id'] = $this->db->insert_id();
			
			return $umRegistro['id'];
		}
		else {
			return $erros;
		}
	}
	
	/**
	 * Recebe um array de registros e insere na base de dados
	 * @param array de beans $registros
	 * @param string o nome alternativo para a chave primaria
	 * @return int/array $id inserido ou (array)$erros com mensagens de erro
	 */
	function insereVarios($registros, $nome_id = 'id', $debug = false) {
		if ($debug) {
			d($registros, 'Inserindo registros');
		}
		$erros = array();
		if (empty($registros)) {
			$erros[] = 'Não foram passados registros.';
			return $erros;
		}
		foreach ($registros as $i => $registro) {
			$erro = $this->valida($registro);
			if (!empty($erro)) {
				$erros[$i] = implode("<br />", $erro);
			}
		}
		
		if (empty($erros)) {
			$campos = array_keys(reset($registros));
			$valores = array();
			foreach ($registros as $registro) {
				$r = array();
				foreach ($registro as $valor) {
					$r[] = $this->db->escape_str($valor);
				}
				$valores[] = "('" . implode("', '", $r) . "')";
			}
			$q = "INSERT INTO " . $this->tabela . "(" . implode(", ", $campos) . ") VALUES " . implode(", ", $valores);
			$this->db->query($q);
			if ($debug) {
				d($this->db->last_query(), 'SQL');
			}
			
			// recupera os ids inseridos
			$ids = array('');
			if (!empty($nome_id)) {
				$this->db->select($nome_id);
				$this->db->order_by($nome_id . ' DESC');
				$rs = $this->db->get($this->tabela, count($registros))->result();
//d($this->db->last_query());
				for ($i = count($rs)-1; $i >= 0; $i--) {
					$ids[] = $rs[$i]->$nome_id;
				}
				array_shift($ids);
			}
			
			if ($debug) {
				d($ids, 'IDS');
			}
			
			return $ids;
		}
		else {
			if ($debug) {
				d($erros, 'Erros');
			}
			return $erros;
		}
	}
	
	/**
	 * Edita um registro baseado no id
	 * @param array $umRegistro
	 * @param mixed $valor
	 * @param string $campo o nome alternativo para a chave primaria
	 * @param bool $debug
	 * @return array $erros para cada campo que não passou da validação 
	 */
	function edita($umRegistro, $valor = null, $campo = 'id', $debug = false) {
		if ($debug) {
			d($umRegistro, "campos");
		}
		$erros = $this->valida($umRegistro, $valor);
		if (empty($erros)) {
			if (!empty($valor)) {
				$this->db->update($this->tabela, $umRegistro, array($campo => $valor));
			}
			else {
				$this->db->update($this->tabela, $umRegistro);
			}
			if ($debug) {
				d($this->db->last_query(), "edita");
			}
			$this->registro = $umRegistro;
		}
		if ($debug) {
			d($erros, "erros");
		}
		return $erros;
	}
	
	/**
	 * Apaga um registro
	 * @param int $id
	 * @param string o campo da condicao
	 * @return int linhas afetadas
	 */
	function apaga($valor, $campo = 'id', $debug = false) {
		if (empty($campo)) {
			$this->db->delete($this->tabela);
		}
		else {
			$this->db->delete($this->tabela, array($campo => $valor));
		}
		$affected_rows = $this->db->affected_rows();
		if ($debug) {
			d($this->db->last_query(), "apaga");
			d($affected_rows, "linhas apagadas");
		}
		return $affected_rows;
	}
		
	/**
	 * Apaga varios registros
	 * @param int $valores
	 * @param string o campo da condicao
	 * @return int linhas afetadas
	 */
	function apagaVarios($valores, $campo = 'id', $debug = false) {
		$this->db->where_in($campo, $valores);
		$this->db->delete($this->tabela);
		$affected_rows = $this->db->affected_rows();
		if ($debug) {
			d($this->db->last_query(), "apaga");
			d($affected_rows, "linhas apagadas");
		}
		return $affected_rows;
	}
	
	/**
	 * Executa uma consulta trazendo os registros com paginação
	 * @param int $pagina
	 * @param int $limite
	 * @return array de object registro
	 */
	function lista($pagina = 1, $limite = -1, $debug = false) {
		if ($limite != -1) {
			$offset = ($pagina - 1) * $limite;
			$this->db->limit($limite, $offset);
		}
		$this->db->from($this->tabela . " " . $this->letra);
		$rs = $this->db->get()->result();
		if ($debug) {
			d($this->db->last_query(), "lista($pagina, $limite)", 1);
		}
		
		foreach ($rs as $c => $r) {
			$rs[$c] = $this->trataRegistro($r);
		}
		if ($debug) {
			d($rs, 'resultado');
		}

		return $rs;
	}
	
	/**
	 * Executa uma consulta trazendo os registros em ordem com paginação
	 * @param string $ordem
	 * @param int $pagina
	 * @param int $limite
	 * @return array de object registro
	 */
	function olista($ordem, $pagina = 1, $limite = -1, $debug = false) {
		$this->db->order_by($ordem);
		return $this->lista($pagina, $limite, $debug);
	}
	
	/**
	 * Executa uma consulta especifica trazendo os registros com paginação
	 * @param string $onde
	 * @param int $pagina
	 * @param int $limite
	 * @return array de object registro
	 */
	function consulta($onde, $pagina = 1, $limite = -1, $debug = false) {
		$this->db->where($onde);
		$rs = $this->lista($pagina, $limite, $debug);
		return $rs;
	}

	/**
	 * Executa uma consulta especifica trazendo os registros em ordem com paginação
	 * @param string $onde
	 * @param string $ordem
	 * @param int $pagina
	 * @param int $limite
	 * @return array de object registro
	 */
	function oconsulta($onde, $ordem, $pagina = 1, $limite = -1, $debug = false) {
		$this->db->order_by($ordem);
		return $this->consulta($onde, $pagina, $limite, $debug);
	}
	
	/**
	 * Recupera um registro pelo id
	 * @param $id
	 * @param string o nome alternativo para a chave primaria
	 * @return object de registro
	 */
	function ve($valor, $campo = 'id', $debug = false) {
		try{
			$rs = $this->consulta(array("$this->letra.$campo" => $valor), 1, 1, $debug);
			return current($rs);
		}
		catch (Exception $e){
			return false;
		}
	}
	
	/**
	 * Formata o bean a partir do registro vindo do banco
	 * @param array $registro
	 * @return object do registro
	 */
	function trataRegistro($registro) {
		return $registro;
	}
	/**
	 * Gera uma slug (link permanente - url amigável) 
	 * @param string $sttring
	 */
	function slug($string, $id = 0) {
		$unPretty = array('/Á/','/Í/','/Ó/','/Ú/','/É/','/Ä/','/Ï/','/Ö/','/Ü/','/Ë/','/À/','/Ì/','/Ò/','/Ù/','/È/','/Ã/','/Õ/','/Â/','/Î/','/Ô/','/Û/','/Ê/','/á/','/í/','/ó/','/ú/','/é/','/ä/','/ï/','/ö/','/ü/','/ë/','/à/','/ì/','/ò/','/ù/','/è/','/ã/','/õ/','/â/','/î/','/ô/','/û/','/ê/','/Ç/','/ç/','/ß/','/\s?-\s?/','/\s?_\s?/','/\s?\/\s?/','/\s?\\\s?/','/\s/','/"/','/\'/');
		$pretty   = array('a','i','o','u','e','a','i','o','u','e','a','i','o','u','e','a','o','a','i','o','u','e','a','i','o','u','e','a','i','o','u','e','a','i','o','u','e','a','o','a','i','o','u','e','C','c','ss','-','-','-','-','-','','');
				
		$slug = strtolower(preg_replace($unPretty, $pretty, $string));
		$s = '';
		for ($i = 0; $i < strlen($slug); $i++) {
			$char = substr($slug, $i, 1);
			$c = ord($char);
			if ($c == 32 || $c == 45 || ($c >= 48 && $c <= 57) || ($c >= 65 && $c <= 90) || ($c >= 97 && $c <= 122)) {
				$s .= $char;
			}
		}
		$slug = $s;
		
		$r = $this->consulta("$this->letra.slug = '$slug' AND $this->letra.id <> $id", 1, 1);
		if (!empty($r)) {
			$this->db->order_by($this->letra . '.slug DESC');	
			$r = $this->consulta("$this->letra.slug LIKE '$slug%' AND $this->letra.id <> $id", 1, 1);
			$string = explode('-', $r[0]->slug);
			$ultimo = (int)(is_numeric(end($string))) ? array_pop($string) : null;
			if (!empty($ultimo)) {
				$slug .= '-' . ++$ultimo;
			}
			else {
				$slug .= '-2';
			}
		}

		return $slug;
	}
	
	/**
	 * Retorna o número total de registros da última consulta no banco de dados
	 * @return Total de registros
	 */
	function getTotalRegistros($debug = false) {
		$query = $this->db->last_query();
		if ($debug) {
			d($query, 'Última query');
		}

		$upperQuery = strtoupper($query);
		$limite = strrpos($upperQuery, "LIMIT");
		$from = strrpos($upperQuery, "FROM");
		if ($debug) {
			d($limite . ', ' . $from, 'Posições (limit, from)');
		}

		if ($limite !== false && $limite > $from) {
			$query = substr($query, 0, $limite - 1);
		}
		if ($debug) {
			d($this->db->last_query(), 'Sem limite');
		}
		$this->db->query($query);
		$total = $this->db->affected_rows();
		if ($debug) {
			d($total, 'Total registros');
		}

		return $total;
	}
	
	/**
	 * Ativa e Desativa um registro
	 * @param int $id
	 * @param array $set
	 * @param string o nome alternativo para a chave primaria
	 * @return true - ativado, false - erro
	 */
	function ativaDesativa($id, $set, $nome_id = 'id') {
		$ok = !empty($id);
		if ($ok) {
			$this->db->update($this->tabela, array('ativo' => $set), "$nome_id = '$id'");
		}

		return $ok;
	}
	
	/**
	 * Forca o download do arquivo
	 * @param string $onde valor do campo
	 * @param string $campoOnde
	 * @param string $campoNome o campo utilizado para o nome do arquivo a ser baixado
	 * @param string $campoArquivo
	 * @param string $caminho
	 */
	function download($onde, $campoOnde = 'id', $campoNome = 'slug', $campoArquivo = 'arquivo', $caminho = 'arquivos/') {
		$r = $this->ve($onde, $campoOnde);
		$nome = $r->$campoNome . '.' . end(explode('.', $r->$campoArquivo));
		$dados = @file_get_contents(site_url($caminho . $r->$campoArquivo)) or @file_get_contents_curl(site_url($caminho . $r->$campoArquivo));
		force_download($nome, $dados);
	}
	
}